﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace SQLPXBase.Factory.Keys
{
    public abstract class BaseLazyKey<PX>
        where PX : Interfaces.ISQLProxyBase, new()
    {
        private PrimaryKey id = null;

        protected ModelFactory<PX> factory;
        protected BaseLazyKey(ModelFactory<PX> factory)
        {
            this.factory = factory;
        }

        public virtual PrimaryKey Id
        {
            get { return id; }
            set
            {
                id = value;
            }
        }
    }

    public class LazyKey<T, PX>:BaseLazyKey<PX> 
        where PX : Interfaces.ISQLProxyBase, new()
        where T : FactorizableModel<PX>
    {
        
        private T model = null;

        public bool AlwaysRetryLazyLoading { get; set; }
        private bool lazyLoadingConsumed = false;

        #region Caching

        public bool UseCacheIfAvailable { get; set; }
        private List<T> cache = null;

        public void SetCache(List<T> cache)
        {
            this.cache = cache;
            if (cache != null)
                UseCacheIfAvailable = true;
        }

        #endregion

        public LazyKey(ModelFactory<PX> factory)
            :base(factory)
        {
            AlwaysRetryLazyLoading = false;
            UseCacheIfAvailable = false;
        }

        public LazyKey(ModelFactory<PX> factory, PrimaryKey id)
            : this(factory)
        {
            Id = id;
        }

        public override PrimaryKey Id
        {
            get
            {
                return base.Id;
            }
            set
            {
                if (Id != value)
                {
                    model = null;
                    lazyLoadingConsumed = false;
                }

                base.Id = value;
            }
        }

        /// <summary>
        /// Returns a model instance, if already loaded previously, returns it from cache
        /// </summary>
        public T Model
        {
            get
            {
                if (model == null && Id!=null && !Id.IsEmpty && (AlwaysRetryLazyLoading || !lazyLoadingConsumed))
                {
                    // tries loading model instance from cache, if specified
                    if (UseCacheIfAvailable && cache != null)
                        model = cache.First(t => t.GetPK() == Id);

                    if (model == null)
                    {
                        lazyLoadingConsumed = true;
                        model = factory.Load<T>(Id);
                    }
                }

                return model;
            }
        }

        #region Overriding Equals

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            LazyKey<T, PX> y = obj as LazyKey<T, PX>;
            if ((object)y == null)
                return false;

            return this.Id == y.Id;
        }

        public bool Equals(LazyKey<T, PX> k)
        {
            if ((object) k == null) return false;
            return this.Id == k.Id;
        }

        public override int GetHashCode()
        {
            throw new NotImplementedException("Lazykey GetHashCode");

            //if (Id == null || Id.IsEmpty) return -1;

            //if (Id.Value < int.MinValue || Id.Value > int.MaxValue)
            //    return 0;

            //return System.Convert.ToInt32(Id.Value);

            
        }

        public override string ToString()
        {
            return string.Format("LazyKey<{1}>: {0}", Id == null || Id.IsEmpty ? "NULL" : Id.ToString(),
                typeof (T).FullName);
        }

        public static bool operator ==(LazyKey<T, PX> a, LazyKey<T, PX> b)
        {
            if (ReferenceEquals(a, b))
                return true;

            if (((object) a == null) || ((object) b == null))
                return false;

            return a.Id == b.Id;
        }

        public static bool operator !=(LazyKey<T, PX> a, LazyKey<T, PX> b)
        {
            return !(a == b);
        }

        //public static bool operator ==(LazyKey<T, PX> a, long? b)
        //{
        //    if ((object)a == null)
        //        return false;

        //    return a.Id == b;
        //}

        //public static bool operator !=(LazyKey<T, PX> a, long? b)
        //{
        //    return !(a == b);
        //}

        public static bool operator ==(LazyKey<T, PX> a, PrimaryKey b)
        {
            if ((object) a == null || a.Id == null || a.Id.IsEmpty)
                return false;

            if (b == null || b.IsEmpty)
                return false;

            return a.Id == b;
        }

        public static bool operator !=(LazyKey<T, PX> a, PrimaryKey b)
        {
            return !(a == b);
        }

        #endregion

        public bool HasValue
        {
            get { return factory != null && Id != null && !Id.IsEmpty; }
        }

        public PrimaryKey IdOrDefault
        {
            get
            {
                return Id != null
                    ? (Id.IsEmpty ? Id.DefaultValue : Id)
                    : ((PrimaryKey) Activator.CreateInstance(Utils.GetDeclaredType(Id))).DefaultValue;
            }
        }

        public void SetModel(T model)
        {
            lazyLoadingConsumed = false;
            if (model == null)
            {
                Id = null;
            }
            else
            {
                this.model = model;
                this.Id = model.GetPK();
            }
        }

        public void RefreshLazy()
        {
            model = null;
            lazyLoadingConsumed = false;
        }

        /// <summary>
        /// Returns a model instance without using the cache
        /// </summary>
        public T FreshModel
        {
            get
            {
                T res = Id!=null && !Id.IsEmpty ? factory.Load<T>(Id) : null;

                // if cache is empty fills cache
                if (model == null) model = res;

                return res;
            }
        }
    }
}
